﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;
using Duellum.Core.ExceptionHelper;

namespace Duellum.Core
{
	[ImmutableObject(true)]
	internal abstract class FunctionAugValue : IAugValue, IXSerializable
	{
		#region Nested Classes
		
		sealed class ConstantFuncValue : FunctionAugValue
		{	
			Func<object> func;
			
			public ConstantFuncValue(Func<object> func)
			{
				this.func = func;
			}

			public override object GetValue(AugObject owner)
			{
				return func();
			}
		}

		sealed class FuncOfOwnerValue : FunctionAugValue
		{	
			Func<AugObject,object> func;
			
			public FuncOfOwnerValue(Func<AugObject,object> func)
			{
				this.func = func;
			}

			public override object GetValue(AugObject owner)
			{
				return func(owner);
			}
		}

		sealed class FuncOfOnwerAndBaseValue : FunctionAugValue
		{	
			Func<AugObject,object,object> func;
			AugProperty ap;
			
			public FuncOfOnwerAndBaseValue(Func<AugObject,object,object> func, AugProperty ap)
			{
				this.func = func;
				this.ap = ap;
			}

			public override object GetValue(AugObject owner)
			{
				var baseValue = owner.GetBaseValue(ap);
				return func(owner, baseValue);
			}
		}
		
		#endregion
		
		public abstract object GetValue(AugObject owner);
		
		protected string Description { get; private set; }

		public override string ToString()
		{
			return string.IsNullOrEmpty(Description) ? base.ToString() : Description;
		}
		
		public XObject ToXml(XName name)
		{
			return new XElement(name, new XCData(this.ToString()));
		}
		
		///Func<object> f1; //independent function
		///Func<AugObject,object> f2; //final value depends on augObject
		///Func<AugObject,object,object> f3; //final value depends on augObject and on base value (it's useful for coercion)
		///TODO: f4 = lambda depends on a 'context'(?)

		static public IAugValue Create(LambdaExpression lambda, AugProperty ap)
		{
			if (!IsValidLambda(lambda)) throw Error.ArgumentNotValid("lambda");
			
			var value = (FunctionAugValue)Create(lambda.Compile(), lambda.Parameters.Count, ap);
			value.Description = lambda.Body.ToString();
			return value;
		}

		static public IAugValue Create(Delegate dlg, AugProperty ap)
		{
			int paramCount;
			if (!IsValidDelegate(dlg, out paramCount)) throw Error.ArgumentNotValid("dlg");
			
			return Create(dlg, paramCount, ap);
		}
		
		private static IAugValue Create(Delegate dlg, int parameterCount, AugProperty ap)
		{
			switch (parameterCount) {
				case 0: return new ConstantFuncValue((Func<object>)dlg);
				case 1: return new FuncOfOwnerValue((Func<AugObject,object>)dlg);
				case 2: return new FuncOfOnwerAndBaseValue((Func<AugObject,object,object>)dlg, ap);
				default: throw new NotSupportedException(); //that should be impossible
			}
		}

		private static bool IsValidLambda(LambdaExpression lambda)
		{
			//I don't validate the return type because it's not really relevant at this moment

			return FuncParametersAreValid(lambda.Parameters.Select(p => p.Type));
		}

		private static bool IsValidDelegate(Delegate dlg, out int parameterCount)
		{
			var invokeDlgInfo = dlg.GetType().GetMethod("Invoke");
			var parameterTypes = invokeDlgInfo.GetParameters().Select(p => p.ParameterType).ToArray();
			
			parameterCount = parameterTypes.Length;
			return FuncParametersAreValid(parameterTypes);
		}

		private static bool FuncParametersAreValid(IEnumerable<Type> parameters)
		{
			using (var it = parameters.GetEnumerator()) {
				if (!it.MoveNext()) return true; //Func<object>;
				
				if (!typeof(AugObject).IsAssignableFrom(it.Current)) return false;
				if (!it.MoveNext()) return true; //Func<AugObject,object>;
				
				if (typeof(object) != it.Current) return false;
				if (!it.MoveNext()) return true; //Func<AugObject,object,object>;
				
				return false; //more than 2 parameters => not supported yet
			}
		}
	}
}
